| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- 'use client';
- import { useEffect, useRef, useState } from 'react';
- import { X } from 'lucide-react';
- import { fetchApi } from '@/lib/utils/client';
- import useAuth from '@/hooks/useAuth';
- import EmojiPicker from '@/app/component/EmojiPicker';
- import { FeedReply } from '@/types/feed/post';
- const MAX_LENGTH = 300;
- type Props = {
- postID: number;
- replyTarget: FeedReply|null;
- onClearReplyTarget: () => void;
- onSubmitted: () => void;
- };
- export default function FeedReplyComposer({ postID, replyTarget, onClearReplyTarget, onSubmitted }: Props) {
- const { member, loginCheck } = useAuth();
- const [content, setContent] = useState('');
- const [submitting, setSubmitting] = useState(false);
- const [error, setError] = useState<string|null>(null);
- const textareaRef = useRef<HTMLTextAreaElement|null>(null);
- const isOver = content.length > MAX_LENGTH;
- useEffect(() => {
- if (replyTarget) {
- textareaRef.current?.focus();
- }
- }, [replyTarget]);
- const handleEmojiSelect = (emoji: string) => {
- const textarea = textareaRef.current;
- if (!textarea) {
- setContent((prev) => prev + emoji);
- return;
- }
- const start = textarea.selectionStart ?? content.length;
- const end = textarea.selectionEnd ?? content.length;
- const next = content.slice(0, start) + emoji + content.slice(end);
- setContent(next);
- requestAnimationFrame(() => {
- textarea.focus();
- const caret = start + emoji.length;
- textarea.setSelectionRange(caret, caret);
- });
- };
- const handleSubmit = async () => {
- if (!loginCheck() || submitting) {
- return;
- }
- const trimmed = content.trim();
- if (trimmed.length === 0) {
- setError('답글 내용을 입력해주세요.');
- return;
- }
- if (isOver) {
- setError(`답글은 최대 ${MAX_LENGTH}자까지 작성할 수 있습니다.`);
- return;
- }
- setSubmitting(true);
- setError(null);
- try {
- const body: { content: string; parentCommentID?: number } = {
- content: trimmed
- };
- if (replyTarget) {
- body.parentCommentID = replyTarget.id;
- }
- const res = await fetchApi(`/api/feed/post/${postID}/comment`, {
- method: 'POST',
- body,
- silent: true
- });
- if (!res.success) {
- setError(res.message ?? '댓글 등록 실패');
- return;
- }
- setContent('');
- onClearReplyTarget();
- onSubmitted();
- } catch (err) {
- console.error(err);
- setError(err instanceof Error ? err.message : '답글 등록 실패');
- } finally {
- setSubmitting(false);
- }
- };
- if (!member) {
- return (
- <div className="feed__reply-composer feed__reply-composer--login">
- <p>답글을 남기려면 로그인이 필요합니다.</p>
- </div>
- );
- }
- return (
- <div className="feed__reply-composer">
- <div className="feed__reply-composer-avatar">
- {member.thumb ? (
- <img src={member.thumb} alt={member.name ?? member.sid} />
- ) : (
- <span className="feed__reply-composer-avatar-fallback">{(member.name ?? member.sid).charAt(0).toUpperCase()}</span>
- )}
- </div>
- <div className="feed__reply-composer-body">
- {replyTarget && (
- <div className="feed__reply-composer-target">
- <span>{(replyTarget.authorName || replyTarget.authorSID) ?? '알 수 없음'}님에게 답글</span>
- <button type="button" onClick={onClearReplyTarget} aria-label="답글 대상 취소">
- <X size={14} />
- </button>
- </div>
- )}
- <textarea
- ref={textareaRef}
- id="feed-reply-composer-content"
- name="content"
- className="feed__reply-composer-input"
- placeholder={replyTarget ? '답글 쓰기...' : '생각을 나눠보세요'}
- value={content}
- onChange={(e) => setContent(e.target.value)}
- rows={2}
- maxLength={MAX_LENGTH + 50}
- disabled={submitting}
- />
- {error && <p className="feed__reply-composer-error">{error}</p>}
- <div className="feed__reply-composer-footer">
- <div className="feed__reply-composer-tools">
- <EmojiPicker onEmojiSelect={handleEmojiSelect} />
- </div>
- <div className="feed__reply-composer-submit">
- <span className={`feed__reply-composer-count${isOver ? ' feed__reply-composer-count--over' : ''}`}>
- {content.length} / {MAX_LENGTH}
- </span>
- <button
- type="button"
- onClick={handleSubmit}
- disabled={submitting || content.trim().length === 0 || isOver}
- className="feed__reply-composer-btn"
- >
- {submitting ? '등록 중...' : '답글'}
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- }
|